/*

This file is part of Ext JS 4

Copyright (c) 2011 Sencha Inc

Contact:  http://www.sencha.com/contact

Commercial Usage
Licensees holding valid commercial licenses may use this file in accordance with the Commercial Software License Agreement provided with the Software or, alternatively, in accordance with the terms contained in a written agreement between you and Sencha.

If you are unsure which license is appropriate for your use, please contact the sales department at http://www.sencha.com/contact.

*/
/**
 * @class Ext.form.field.Picker
 * @extends Ext.form.field.Trigger
 * <p>An abstract class for fields that have a single trigger which opens a "picker" popup below
 * the field, e.g. a combobox menu list or a date picker. It provides a base implementation for
 * toggling the picker's visibility when the trigger is clicked, as well as keyboard navigation
 * and some basic events. Sizing and alignment of the picker can be controlled via the {@link #matchFieldWidth}
 * and {@link #pickerAlign}/{@link #pickerOffset} config properties respectively.</p>
 * <p>You would not normally use this class directly, but instead use it as the parent class for
 * a specific picker field implementation. Subclasses must implement the {@link #createPicker} method
 * to create a picker component appropriate for the field.</p>
 *
 */
Ext.define('Ext.form.field.Picker', {
    extend: 'Ext.form.field.Trigger',
    alias: 'widget.pickerfield',
    alternateClassName: 'Ext.form.Picker',
    requires: ['Ext.util.KeyNav'],

    /**
     * @cfg {Boolean} matchFieldWidth
     * Whether the picker dropdown's width should be explicitly set to match the width of the field.
     * Defaults to <tt>true</tt>.
     */
    matchFieldWidth: true,

    /**
     * @cfg {String} pickerAlign
     * The {@link Ext.core.Element#alignTo alignment position} with which to align the picker. Defaults
     * to <tt>"tl-bl?"</tt>
     */
    pickerAlign: 'tl-bl?',

    /**
     * @cfg {Array} pickerOffset
     * An offset [x,y] to use in addition to the {@link #pickerAlign} when positioning the picker.
     * Defaults to undefined.
     */

    /**
     * @cfg {String} openCls
     * A class to be added to the field's {@link #bodyEl} element when the picker is opened. Defaults
     * to 'x-pickerfield-open'.
     */
    openCls: Ext.baseCSSPrefix + 'pickerfield-open',

    /**
     * @property isExpanded
     * @type Boolean
     * True if the picker is currently expanded, false if not.
     */

    /**
     * @cfg {Boolean} editable <tt>false</tt> to prevent the user from typing text directly into the field;
     * the field can only have its value set via selecting a value from the picker. In this state, the picker
     * can also be opened by clicking directly on the input field itself.
     * (defaults to <tt>true</tt>).
     */
    editable: true,


    initComponent: function() {
        this.callParent();

        // Custom events
        this.addEvents(
            /**
             * @event expand
             * Fires when the field's picker is expanded.
             * @param {Ext.form.field.Picker} field This field instance
             */
            'expand',
            /**
             * @event collapse
             * Fires when the field's picker is collapsed.
             * @param {Ext.form.field.Picker} field This field instance
             */
            'collapse',
            /**
             * @event select
             * Fires when a value is selected via the picker.
             * @param {Ext.form.field.Picker} field This field instance
             * @param {Mixed} value The value that was selected. The exact type of this value is dependent on
             * the individual field and picker implementations.
             */
            'select'
        );
    },


    initEvents: function() {
        var me = this;
        me.callParent();

        // Add handlers for keys to expand/collapse the picker
        me.keyNav = Ext.create('Ext.util.KeyNav', me.inputEl, {
            down: function() {
                if (!me.isExpanded) {
                    // Don't call expand() directly as there may be additional processing involved before
                    // expanding, e.g. in the case of a ComboBox query.
                    me.onTriggerClick();
                }
            },
            esc: me.collapse,
            scope: me,
            forceKeyDown: true
        });

        // Non-editable allows opening the picker by clicking the field
        if (!me.editable) {
            me.mon(me.inputEl, 'click', me.onTriggerClick, me);
        }

        // Disable native browser autocomplete
        if (Ext.isGecko) {
            me.inputEl.dom.setAttribute('autocomplete', 'off');
        }
    },


    /**
     * Expand this field's picker dropdown.
     */
    expand: function() {
        var me = this,
            bodyEl, picker, collapseIf;

        if (me.rendered && !me.isExpanded && !me.isDestroyed) {
            bodyEl = me.bodyEl;
            picker = me.getPicker();
            collapseIf = me.collapseIf;

            // show the picker and set isExpanded flag
            picker.show();
            me.isExpanded = true;
            me.alignPicker();
            bodyEl.addCls(me.openCls);

            // monitor clicking and mousewheel
            me.mon(Ext.getDoc(), {
                mousewheel: collapseIf,
                mousedown: collapseIf,
                scope: me
            });
            Ext.EventManager.onWindowResize(me.alignPicker, me);
            me.fireEvent('expand', me);
            me.onExpand();
        }
    },

    onExpand: Ext.emptyFn,

    /**
     * @protected
     * Aligns the picker to the
     */
    alignPicker: function() {
        var me = this,
            picker, isAbove,
            aboveSfx = '-above';

        if (this.isExpanded) {
            picker = me.getPicker();
            if (me.matchFieldWidth) {
                // Auto the height (it will be constrained by min and max width) unless there are no records to display.
                picker.setSize(me.bodyEl.getWidth(), picker.store && picker.store.getCount() ? null : 0);
            }
            if (picker.isFloating()) {
                picker.alignTo(me.inputEl, me.pickerAlign, me.pickerOffset);

                // add the {openCls}-above class if the picker was aligned above
                // the field due to hitting the bottom of the viewport
                isAbove = picker.el.getY() < me.inputEl.getY();
                me.bodyEl[isAbove ? 'addCls' : 'removeCls'](me.openCls + aboveSfx);
                picker.el[isAbove ? 'addCls' : 'removeCls'](picker.baseCls + aboveSfx);
            }
        }
    },

    /**
     * Collapse this field's picker dropdown.
     */
    collapse: function() {
        if (this.isExpanded && !this.isDestroyed) {
            var me = this,
                openCls = me.openCls,
                picker = me.picker,
                doc = Ext.getDoc(),
                collapseIf = me.collapseIf,
                aboveSfx = '-above';

            // hide the picker and set isExpanded flag
            picker.hide();
            me.isExpanded = false;

            // remove the openCls
            me.bodyEl.removeCls([openCls, openCls + aboveSfx]);
            picker.el.removeCls(picker.baseCls + aboveSfx);

            // remove event listeners
            doc.un('mousewheel', collapseIf, me);
            doc.un('mousedown', collapseIf, me);
            Ext.EventManager.removeResizeListener(me.alignPicker, me);
            me.fireEvent('collapse', me);
            me.onCollapse();
        }
    },

    onCollapse: Ext.emptyFn,


    /**
     * @private
     * Runs on mousewheel and mousedown of doc to check to see if we should collapse the picker
     */
    collapseIf: function(e) {
        var me = this;
        if (!me.isDestroyed && !e.within(me.bodyEl, false, true) && !e.within(me.picker.el, false, true)) {
            me.collapse();
        }
    },

    /**
     * Return a reference to the picker component for this field, creating it if necessary by
     * calling {@link #createPicker}.
     * @return {Ext.Component} The picker component
     */
    getPicker: function() {
        var me = this;
        return me.picker || (me.picker = me.createPicker());
    },

    /**
     * Create and return the component to be used as this field's picker. Must be implemented
     * by subclasses of Picker.
     * @return {Ext.Component} The picker component
     */
    createPicker: Ext.emptyFn,

    /**
     * Handles the trigger click; by default toggles between expanding and collapsing the
     * picker component.
     */
    onTriggerClick: function() {
        var me = this;
        if (!me.readOnly && !me.disabled) {
            if (me.isExpanded) {
                me.collapse();
            } else {
                me.expand();
            }
            me.inputEl.focus();
        }
    },

    mimicBlur: function(e) {
        var me = this,
            picker = me.picker;
        // ignore mousedown events within the picker element
        if (!picker || !e.within(picker.el, false, true)) {
            me.callParent(arguments);
        }
    },

    onDestroy : function(){
        var me = this;
        Ext.EventManager.removeResizeListener(me.alignPicker, me);
        Ext.destroy(me.picker, me.keyNav);
        me.callParent();
    }

});


